import os
import shutil
import binascii
import datetime
import time
import requests
import sys
import logging as log
from contextlib import closing
# from fake_useragent import UserAgent
from dateutil.relativedelta import relativedelta
from typing import List, Tuple, Dict
from lxml import etree
import re
# 把当前运行目录加入到系统环境变量
cwd = os.getcwd()
sys.path.append(cwd)
base_dir = cwd + '../' # 当前目录的上级目录

# 屏蔽warning信息
# requests.packages.urllib3.disable_warnings()

"""
函数结构：
def function_name(param1, param2, ...):
    '''
    1. 作用：...
    2. 参数：...
        参数1：...
        参数2：...
    3. 返回值：...
    '''
    函数主体...
"""
##########字符串相关##########
def str_han(str1):
    '''
    1.作用:接收一个字符串返回该字符串中有多少中文字符
    2.参数：
        str1 ：字符串
    3.返回值：int
    '''
    return len(re.findall('([\u4e00-\u9fa5])',str1))

def str_num(str1):
    '''
    1.作用:接收一个字符串返回该字符串中有多少数字
    2.参数：
        str1 ：字符串
    3.返回值：int
    '''
    str_sum = 0
    dig_sum = 0
    spa_sum = 0
    other_sum = 0
    for strs in str1:
    #如果在字符串中有字符，那么字符的数量+1
        if strs.isalpha():
            str_sum += 1
    #如果在字符串中有数字，那么数字的数量+1
        elif strs.isdigit():
            dig_sum += 1
    #如果在字符串中有空格，那么空格的数量+1
        elif strs == ' ':
            spa_sum += 1
     #如果在字符串中有特殊字符那么特殊字符的数量+1
        else:
            dig_sum += 1
    return dig_sum
########## 文件相关 ##########
def file_write(file_path, content, mode='a', encoding='utf8'):
    '''
    1. 作用:创建文件并把content的内容写入到文件file_path中
    2. 参数:
        file_path:文件路径
        content:文件写入的内容
        mode:文件读写的模式,默认为'w'
        encoding:文件编码,默认为'utf8'
    3. 返回值:None
    '''
    f = open(file=file_path, mode=mode, encoding=encoding)
    f.write(content)
    f.close()

def file_read(file_path, mode='r', encoding='utf8'):
    '''
    1. 作用:读取文件所有内容
    2. 参数:
        file_path:文件路径
        mode:文件读方式,默认为'r'
        encoding:文件编码,默认为'utf8'
    3. 返回值:文件所有内容
    '''
    f = open(file_path, mode=mode, encoding=encoding)
    file_content = f.read()
    f.close()
    return file_content

def file_readlines(file_path, mode='r', encoding='utf8'):
    '''
    1. 作用:逐行读取文件内容，并保存到列表中，列表的一个元素为文件一行的内容
    2. 参数：
        file_path:文件路径
        mode:文件读方式,默认为'r'
        encoding:文件编码,默认为'utf8'
    3. 返回值:逐行读取文件内容的列表
    '''
    f = open(file_path, mode=mode, encoding=encoding)
    file_content = f.readlines()
    f.close()
    return file_content

def get_list_file(path, ext='.jpg'):
    '''
    1.作用:遍历文件夹下的所有指定后缀名的文件的具体路径
    2.参数： 
        path:需要遍历的目录， 
        ext:文件后缀名，默认为'.jpg'
    3.返回值:文件的绝对路径
    '''
    list_file = []
    for root, dirs, files in os.walk(path):
        for filename in files:
            file_ext = filename.endswith(ext)
            if file_ext:
                fpath = os.path.join(root, filename)
                list_file.append(fpath)
    return list_file

def get_file_size(file_path):
    '''
    1. 获取文件大小，单位byte
    2. 参数:file_path:文件路径
    3. 返回值:文件大小,单位为字节;若文件不存在，返回None
    '''
    if os.path.exists(file_path):
        file_size = os.path.getsize(file_path)
        return file_size

def get_filename(file_path):
    '''
    1. 作用:获取文件名,不包含后缀名
    2. 参数:file_path:文件路径
    3. 返回值:文件名
    '''
    filename = os.path.basename(file_path)
    filename = filename.split('.')[0]
    return filename

def file_rename(file_path, new_file_path):
    '''
    1. 作用:更改文件名
    2. 参数:
        file_path:文件路径
        new_file_path:新文件路径
    3. 返回值:更改后的文件路径
    '''
    os.rename(file_path, new_file_path)

def get_execute_filename():
    '''
    1. 作用:获取当前执行程序的文件名
    2. 参数:无
    3. 返回值:当前执行程序的文件名,不包含后缀名
    '''
    return get_filename(__file__)


def get_abspath(path):
    '''
    1. 作用：获取文件的绝对路径
    2. 参数：
        path：文件名
    3. 返回值：
        文件的绝对路径
    '''
    return os.path.abspath(path)

def copyfile(old_filepath, new_filepath):
    '''
    1. 作用：复制文件
    2. 参数：参数：
        old_filepath：需要复制的文件名
        new_filepath：复制后的文件名
    3. 返回值：
        True：复制成功
        False：复制失败
    '''
    if os.path.exists(old_filepath) and not os.path.exists(new_filepath):
        shutil.copyfile(old_filepath, new_filepath)
        return True
    return False

def movefile(old_filepath, new_filepath):
    '''
    1. 作用:移动文件
    2. 参数：
        old_filepath：需要移动的文件路径
        new_filepath：移动后的文件路径
    3. 返回值：
        True：移动成功
        False：移动失败
    '''
    if os.path.exists(old_filepath) and not os.path.exists(new_filepath):
        shutil.move(old_filepath, new_filepath)
        return True
    return False

def delete_file(filepath):
    '''
    1. 作用：删除文件
    2. 参数：
        filepath：文件名
    3. 返回值：
        True：修改成功
        False：修改失败
    '''
    if os.path.exists(filepath):
        os.remove(filepath)
        return True
    return False

def init_dir(dirpath):
    '''
    1. 功能：用于在指定目录下新建或清空一个文件夹
    2. 参数：dirpath：文件夹路径
    3. 返回值：为处理结果
    4. 函数说明：
        os.path.join :将目录和文件名合成为一个路径
        os.path.isdir : 判断路径是否为文件夹
        os.path.isfile: 判断路径是否为文件
        os.remove :删除文件
        os.makedir:创建文件
        shutil.rmtree:清空文件夹
    '''
    if os.path.exists(dirpath):
        ls = os.listdir(dirpath)
        for i in ls:
            d_path = os.path.join(dirpath, i)
            if os.path.isdir(d_path):
                shutil.rmtree(d_path)  # 删除文件夹
            else:
                os.remove(d_path)  # 删除文件
        return '文件已清空'
    else:
        os.makedirs(dirpath)
        return '文件已新建'

def hex_to_bin(str_i):
    '''
    1. 作用：用于将十六进制转化为二进制
    2. 参数：str_i:十六进制字符串
    3. 返回值 ：二进制字符串
    4. 方法：
        int(x, base=):
          x -- 字符串或数字
          base -- 进制数，默认十进制,如果带base，要求x 为字符串。
        bin(): 返回一个整数 int 或者长整数 long int 的二进制表示.
    '''
    str_o = (bin(int(str_i, 16)).replace('0b', '')).rjust(len(str_i) * 4, '0')
    return str_o

def un_ascii(a):
    '''
    1. 作用：用于将十六进制字符串转化为正常字符串
    2. 参数：a:十六进制字符串
    3. 返回：正常字符串
    '''
    list_double_number = []
    str_list = []
    for i in range(len(a)+1):
        if i %2 ==0:
            list_double_number.append(i)

    for i in range(len(list_double_number)-1):
        item = a[list_double_number[i]:list_double_number[i+1]]
        ret = str(binascii.a2b_hex(item))[2:3]
        str_list.append(ret)
    str_ret = ''.join(str_list)
    return str_ret

########## 文件夹相关 ##########
def folder_exist(dir_path):
    '''
    1. 作用:判断文件夹路径是否存在,不存在则创建
    2. 参数:dir_path:文件夹路径
    3. 返回值:None
    '''
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)

def folder_delete(dir_path):
    '''
    1. 作用:删除文件夹下的所有文件夹
    2. 参数:dir_path:文件夹路径
    3. 返回值:
        删除成功:True
        删除失败:False
    '''
    os.removedirs(dir_path)

########## 下载文件相关 ##########

def csv_file_write(content):
    ''' 
    1. 作用:向csv文件写入内容
    2. 参数:content:写入的文件内容
    3. 返回值:无
    '''
    file_path = base_dir+'/download_incomplete.csv'
    file_write(file_path, content, 'a')

def verify_file(url, file_path):
    '''
    1. 作用:验证文件是否下载完成
    2. 参数:
        url:需要下载的文件链接
        file_path:本地文件存储路径
    3. 返回值:无
    '''
    file_length = get_file_size(file_path)
    res = requests.head(url, stream=True, verify=False)
    total_size = int(res.headers['Content-Length'])
    if str(file_length) != str(total_size):
        content = url + ',' + file_path + ',' + str(file_length) + ',' + str(total_size) + '\n'
        csv_file_write(content)

def download(url, file_path, status=1):
    '''
    1. 作用:断点续传方式下载文件
    2. 参数:
        url:需要下载的文件链接
        file_path:存储到本地的文件路径
        status:1:用断点续传,0:不用断点续传，文件重新开始下载
    3. 返回值:无
    '''
    if status == 0:
        dir_name, file_name = os.path.split(file_path)
        DownloadFile(url, dir_name, file_name)
        return
    try:
        # 第一次请求是为了得到文件总大小
        res = requests.head(url, stream=True, verify=False, timeout=10)
        total_size = int(res.headers['Content-Length'])

        # 看看本地文件下载了多少
        if os.path.exists(file_path):
            temp_size = os.path.getsize(file_path)  # 本地已经下载的文件大小
        else:
            temp_size = 0

        # 显示一下下载了多少
        # print(temp_size)
        # print(total_size)
        if total_size > temp_size:
            # 核心部分，这个是请求下载时，从本地文件已经下载过的后面下载
            headers = {'Range': 'bytes=%d-' % temp_size}
            # 重新请求网址，加入新的请求头的
            r = requests.get(url, stream=True, verify=False, headers=headers, timeout=10)
            # "ab"表示追加形式写入文件
            with open(file_path, "ab") as f:
                for chunk in r.iter_content(chunk_size=1024):
                    if chunk:
                        temp_size += len(chunk)
                        f.write(chunk)
                        f.flush()

                        ###这是下载实现进度显示####
                        done = int(50 * temp_size / total_size)
                        sys.stdout.write("\r[%s%s] %d%%" % ('█' * done, ' ' * (50 - done), 100 * temp_size / total_size))
                        sys.stdout.flush()
    except:
        content = url + ',' + file_path + '\n'
        csv_file_write(content)
    verify_file(url, file_path)

#http请求超时设置
timeout = 10
headers = {}
#下载
def DownloadFile(img_url, dir_name, img_name):
    # check_download_dir(folder_name)
    try:
        with closing(requests.get(img_url, stream=True, headers=headers, timeout=timeout)) as r:
            rc = r.status_code
            if 299 < rc or rc < 200:
                print('returnCode%s\t%s' % (rc, img_url))
                return
            content_length = int(r.headers.get('content-length', '0'))

            if content_length == 0:
                print('size0\t%s' % img_url)
                # return
            try:
                with open(os.path.join(dir_name, img_name), 'wb') as f:
                    for data in r.iter_content(1024):
                        f.write(data)
            except:
                # print('save fail \t%s' % img_url)
                log.error('save fail \t%s' % img_url, exc_info=True)
    except:
        # print('requests fail \t%s' % img_url)

        log.error('requests fail \t%s' % img_url, exc_info=True)

def verify_proxy(proxy):
    """
        1. 作用：检验代理是否可用
        2. 参数：proxy: 代理ip地址
        3. 返回值：True：代理ip可用，False：代理ip不可用
    """
    if isinstance(proxy, bytes):
        proxy = proxy.decode('utf8')
    proxies = {"http": "http://{proxy}".format(proxy=proxy)}
    try:
        # 超过10秒的代理就不要了
        r = requests.get('http://httpbin.org/ip', proxies=proxies, timeout=10, verify=False)
        if r.status_code == 200:
            # logger.info('%s is ok' % proxy)
            return True
    except Exception as e:
        # logger.error(str(e))
        return False

def tcp_connect(proxy):
    """
        TCP 三次握手
        1. 作用：查看代理ip是否能通信
        2. 参数：proxy: 代理ip
        3. 返回值：True：能通信，False：不能通信
    """
    from socket import socket, AF_INET, SOCK_STREAM
    s = socket(AF_INET, SOCK_STREAM)
    ip, port = proxy.split(':')
    result = s.connect_ex((ip, int(port)))
    return True if result == 0 else False

def verify_proxy_format(proxy):
    """
        1. 作用：检查代理ip格式是否正确
        2. 参数： proxy:代理ip地址
        3. 返回值：True：格式正确，False：格式不正确
    """
    import re
    verify_regex = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}"
    _proxy = re.findall(verify_regex, proxy)
    return True if len(_proxy) == 1 and _proxy[0] == proxy else False

def get_html_tree(url, **kwargs):
    """
        1. 作用：获取html dom树
        2. 参数： url:
                kwargs:
        3. 返回值：html dom树
    """
    header = {'Connection': 'keep-alive',
              'Cache-Control': 'max-age=0',
              'Upgrade-Insecure-Requests': '1',
              'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko)',
              'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
              'Accept-Encoding': 'gzip, deflate, sdch',
              'Accept-Language': 'zh-CN,zh;q=0.8',
              }
    time.sleep(2)

    html = requests.get(url=url, header=header).content
    return etree.HTML(html)

########## 时间相关 ###########
def get_utc_time(str_time):
    '''
        1. 作用:把北京时间字符串转换为utc时间字符串
        2. 参数:str_time:北京时间字符串
        3. 返回值:utc时间字符串
    '''
    str_time = datetime.datetime.strptime(str_time, '%Y-%m-%d %H:%M:%S')
    utc_time = str_time + datetime.timedelta(hours=-8)
    str_utc_time = datetime.datetime.strftime(utc_time, "%Y-%m-%d %H:%M:%S")
    return str_utc_time

def unix_to_date(str_unix):
    '''
    1. 作用：把unix时间戳转为年月日字符串
    2. 参数：str_unix:unix时间戳
    3. 返回值：年月日字符串
    '''
    time_array = time.localtime(str_unix)
    str_time = time.strftime("%Y%m%d", time_array)
    return str_time

def get_yesterday(current_time):
    '''
    1. 作用:获取前一天的年月日字符串
    2. 参数:str_time:时间字符串
    3. 返回值:前一天的字符串
    '''
    now_time = datetime.datetime.strptime(current_time, '%Y%m%d')
    yesterday = now_time + datetime.timedelta(days=-1)
    str_yesterday = datetime.datetime.strftime(yesterday, "%Y-%m-%d")
    return str_yesterday

def get_next_month(current_month):
    '''
    1. 作用：获取下个月的字符串
    2. 参数：current_time：年月字符串
    3. 放回值：下个月字符串
    '''
    current_month = datetime.datetime.strptime(current_month, '%Y%m')
    next_month = current_month + relativedelta(months=+1)
    next_month = datetime.datetime.strftime(next_month, "%Y%m")
    return next_month

def get_now_time():
    '''
    1. 作用:获取当前时间，精确到小时
    2. 参数:无
    3. 返回值:当前时间的字符串
    '''
    str_now_time = datetime.datetime.now().strftime('%Y%m%d%H')
    return str_now_time

'''
# 初始化日志对象
logging.basicConfig(
    level = logging.INFO, # 日志级别
    format = '%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', # 日志格式, 时间、代码所在文件名、代码行号、日志级别名字、日志信息
    datefmt = '%a, %d %b %Y %H:%M:%S', # 打印日志的时间
    filename = 'logs/report.log', # 日志文件存放的目录（目录必须存在）及日志文件名
    filemode = 'a' # 打开日志文件的方式
)
'''

# from fake_useragent import UserAgent

# ua = UserAgent()
# print(ua.ie)   # 随机打印ie浏览器任意版本
# print(ua.firefox) # 随机打印firefox浏览器任意版本
# print(ua.chrome)  # 随机打印chrome浏览器任意版本
# print(ua.random)  # 随机打印任意厂家的浏览器